using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Gko.Utils;
using Gko.Wpf.Mvvm.Interfaces;

namespace Gko.Wpf.Mvvm
{
	public abstract class DialogServiceBase : IDialogService
	{
		private readonly IViewFactory viewFactory;
		private readonly Stack<Window> windowsStack = new Stack<Window>();
		private readonly Dictionary<Tuple<Type, Guid>, Type> windowViewModelToWindow = new Dictionary<Tuple<Type, Guid>, Type>();

		protected DialogServiceBase(IViewFactory viewFactory)
		{
			this.viewFactory = viewFactory;
		}

		public Window TopWindow
		{
			get
			{
				return this.windowsStack.Count == 0 ? null : this.windowsStack.Peek();
			}
		}

		#region IDialogService Members

		public bool? ShowDialog<TViewModel>(TViewModel viewModel) where TViewModel : class, IModalViewModel
		{
			var window = this.CreateWindow(viewModel);
			this.SubscribeToEvents(window, viewModel);
			window.Owner = this.TopWindow;
			this.windowsStack.Push(window);
			return window.ShowDialog();
		}

		public void ShowWindow<TViewModel>(TViewModel viewModel) where TViewModel : class, IWindowViewModel
		{
			var window = this.CreateWindow(viewModel);
			this.SubscribeToEvents(window, viewModel);
			this.windowsStack.Push(window);
			window.Show();
		}

		public void ShowMainView<TViewModel>(TViewModel viewModel) where TViewModel : class, IModalViewModel
		{
			var window = this.CreateWindow(viewModel);
			this.SubscribeToEvents(window, viewModel);
			Application.Current.MainWindow = window;
			Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
			this.windowsStack.Push(window);
			window.Show();
		}

		public void ShowMainView<TViewModel>(Window window, TViewModel viewModel) where TViewModel : class, IModalViewModel
		{
			this.viewFactory.InitializeView(window, viewModel);
			this.SubscribeToEvents(window, viewModel);
			Application.Current.MainWindow = window;
			Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
			this.windowsStack.Push(window);
			window.Show();
		}

		public void ShowSplash<TViewModel>(TViewModel viewModel) where TViewModel : class, IWindowViewModel
		{
			Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;

			this.ShowWindow(viewModel);

			Application.Current.MainWindow = null;
		}

		public void AttachWindow<TViewModel, TWindow>(Guid id) where TViewModel : class, IWindowViewModel where TWindow : Window
		{
			this.windowViewModelToWindow[Tuple.Create(typeof(TViewModel), id)] = typeof(TWindow);
		}

		public bool ? ShowDialog<TViewModel>(TViewModel viewModel, Guid id) where TViewModel : class, IModalViewModel
		{
			var window = this.CreateWindow(viewModel, id);
			this.SubscribeToEvents(window, viewModel);
			window.Owner = this.TopWindow;
			this.windowsStack.Push(window);
			return window.ShowDialog();
		}

		public void ShowWindow<TViewModel>(TViewModel viewModel, Guid id) where TViewModel : class, IWindowViewModel
		{
			var window = this.CreateWindow(viewModel, id);
			this.SubscribeToEvents(window, viewModel);
			this.windowsStack.Push(window);
			window.Show();
		}

		#endregion

		private void SubscribeToEvents(Window view, IModalViewModel viewModel)
		{
			view.Activated += (s, e) => this.OnViewActivated(view, viewModel);
			view.Deactivated += (s, e) => this.OnViewDeactivated(view, viewModel);
			view.Closing += (s, e) => viewModel.OnViewClosing(e);
			view.Closed += (s, e) => this.OnViewClosed(view, viewModel);
			viewModel.CloseView += (s, e) => OnCloseModalView(view, viewModel);
		}

		private void SubscribeToEvents(Window view, IWindowViewModel viewModel)
		{
			view.Activated += (s, e) => viewModel.OnActivated();
			view.Deactivated += (s, e) => viewModel.OnDeactivated();
			view.Closing += (s, e) => viewModel.OnViewClosing(e);
			view.Closed += (s, e) => this.OnViewClosed(view, viewModel);
			viewModel.CloseView += (s, e) => view.Close();
		}

		private Window CreateWindow<TViewModel>(TViewModel viewModel) where TViewModel : class, IViewModel
		{
			return (Window)this.viewFactory.CreateView(viewModel);
		}

		private Window CreateWindow<TViewModel>(TViewModel viewModel, Guid id) where TViewModel : class, IViewModel
		{
			var windowType = this.windowViewModelToWindow.GetValueOrDefault(Tuple.Create(viewModel.GetType(), id));
			var window = (Window)Activator.CreateInstance(windowType);
			this.viewFactory.InitializeView(window, viewModel);
			return window;
		}

		//private void OnViewSizeChanged(Window window, IModalViewModel viewModel)
		//{
		//    this.CheckTopWindow(window);
		//    viewModel.OnSizeChanged(window.Width, window.Height);
		//}

		//private void OnViewLocationChanged(Window window, IModalViewModel viewModel)
		//{
		//    this.CheckTopWindow(window);
		//    viewModel.OnLocationChanged(window.Left, window.Top);
		//}

		private void OnViewDeactivated(Window window, IModalViewModel viewModel)
		{
			// TODO: investigate: Always throws, window and TopWindow differs by one step
			//this.CheckTopWindow(window); 
			viewModel.OnDeactivated();
		}

		private void OnViewActivated(Window window, IModalViewModel viewModel)
		{
			// TODO: investigate: Always throws, window and TopWindow differs by one step
			//this.CheckTopWindow(window);
			viewModel.OnActivated();
		}

		private void OnViewClosed(Window window, IWindowViewModel viewModel)
		{
			this.CheckTopWindow(window);
			this.windowsStack.Pop();
			viewModel.OnViewClosed();
		}

		private void CheckTopWindow(Window window)
		{
			if (this.TopWindow != window) {
				throw new InvalidOperationException("Window stack corrupted");
			}
		}


		private static void OnCloseModalView<TViewModel>(Window view, TViewModel viewModel)
			where TViewModel : class, IModalViewModel
		{
			if (viewModel.DialogResult.HasValue) {
				view.DialogResult = viewModel.DialogResult;
			} else {
				view.Close();
			}
		}
	}
}